﻿//////////////////////////////////////////////////////////////////////////////
//
// Copyright © 1998-2024 Glodon Company Limited.
//
// Licensed under the MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the “Software”),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//////////////////////////////////////////////////////////////////////////////
#include "OpenFileCmd.h"
#include "IDocument.h"
#include "IActionManager.h"
#include "IApplication.h"
#include "IUiDocumentViewManager.h"
#include "IMainWindow.h"
#include "IUiDocument.h"
#include "IUiView.h"
#include "ModelViewDlgUtils.h"
#include "PropertyPalette.h"
#include "UiCommonDialog.h"
#include "GcmpCommandNames.h"
#include "FilePath.h"
#include "IFileExtensions.h"
#include "IDocumentManager.h"
#include "GbmpFileSystem.h"
#include "UiTransactionErrorProcessor.h"
#include "GbmpUiPlatformCommandRegister.h"
#include "IFamilyConfigElement.h"

#include "FileRestoreUtils.h"
#include "UserTransactionUtils.h"
#include "IRibbon.h"
#include "IUIConfiguration.h"

#include "CommandParameters.h"
#include "GcmpBuiltInCategoryUniIdentities.h"
#include "GcmpCommandState.h"
#include "NdbStrategy.h"
#include "UiPlatformUtils.h"
#include "FamilyDocumentInitializer.h"
#include "EnableCompileWarning_The_LAST_IncludeInCpp.h"

using namespace gcmp;
using namespace gfam;

CREATE_DEBUG_MODE(OpenFileBySavedVersion, L"按照保存的版本读取文件中数据", gcmp::DebugModeGroup::DMGT_DATA_STORAGE, L"GDMPLab", L"2023-12-20", L"用户可以选择指定版本号（保存版本）打开文件，需要和调试模式\"增量存储测试\"一起使用。\n不打开等效于取得最后一次保存的数据。");

CREATE_DEBUG_MODE(ShowFileNameWithoutFolder, L"只显示需要恢复的文件名不包含目录", gcmp::DebugModeGroup::DMGT_DATA_STORAGE, L"GDMPLab", L"2023-12-20", L"自动化测试打开此Mode，即可避免拿到绝对路径");

EXPOSE_DEBUG_MODE_EXPORT(IgnoreModuleClassSchema, GCMP_DATABASE_EXPORT);
EXPOSE_DEBUG_MODE_EXPORT(InterProfessional, GCMP_MODEL_INTERFACE_EXPORT);

CREATE_DEBUG_MODE_EXPORT(RstoreToCopyOfUnsavedFile, L"拷贝出一个新文件来做恢复", gcmp::DebugModeGroup::DMGT_DATA_STORAGE, L"GDMPLab", L"2023-12-20"
    , L"拷贝出一个新文件来做恢复");

CREATE_DEBUG_MODE_EXPORT(EnableLazyLoadInGbmpWhenOpenFile, L"在Gbmp中打开延迟加载模式", gcmp::DebugModeGroup::DMGT_DATA_STORAGE, L"GDMPLab", L"2023-12-20"
    , L"在Gbmp中打开延迟加载模式");


OpenFileCmd::OpenFileCmd()
    : CommandBase(ID_CMD_OPEN_FILE, true)
{
}

OpenFileCmd::~OpenFileCmd()
{
}

bool OpenFileCmd::IsEnabled() const
{
    return !GcmpCommandState::IsInAnimationEnvironment();
}

bool OpenFileCmd::IsVisible() const
{
    return !GcmpCommandState::IsInAnimationEnvironment();
}

namespace
{
    // 崩溃恢复新文件名是否合法
    // 1. 是否为空  2. 路径是否已存在
    bool IsNewFileNameLegal(const std::wstring& newFileName, const std::wstring filePathName, std::wstring& newFilePathName)
    {
        if (newFileName == L"")
        {
            UiCommonDialog::ShowMessageBox(GBMP_TR(L"新文件名为空"), GBMP_TR(L"新文件名为空"), (int)UiCommonDialog::ButtonType::Cancel);
            return false;
        }
        std::wstring dir = FilePath(filePathName).GetDirectoryName();
        std::wstring ext = FilePath(filePathName).GetExtension();
        newFilePathName = FilePath::Combine(dir, newFileName+ext);
        if (FileInfo(newFilePathName).Exists())
        {
            UiCommonDialog::ShowMessageBox(GBMP_TR(L"新文件路径已存在"), GBMP_TR(L"新文件路径已存在"), (int)UiCommonDialog::ButtonType::Cancel);
            return false;
        }
        return true;
    }
}


gcmp::OwnerPtr<IAction> OpenFileCmd::ExecuteCommand(const gcmp::CommandParameters& cmdParams)
{
    if (DEBUG_MODE(RstoreToCopyOfUnsavedFile))
    {
        FileRestoreUtils::EnableRestoreOnNewFile(true);
    }
    else
    {
        FileRestoreUtils::EnableRestoreOnNewFile(false);
    }

    bool restoreOnNewFile = FileRestoreUtils::IsRestoreOnNewFileEnabled();
    std::wstring newFileName;
    std::wstring newFilePathName;
    std::wstring indicatorPath;
    std::wstring newIndicatorPathName;

    IUiDocumentViewManager* pUiDocViewMgr = IUiDocumentViewManager::Get();
    if (!pUiDocViewMgr)
        return nullptr;

    // 注册检测器
    gfam::InitFamilyDocumentEnvironment();

    bool interProfessional = false;
    bool restore = false;
    const int nCmdParamsCnt = (int)cmdParams.size();

    if (DEBUG_MODE(InterProfessional))
    {
        interProfessional = true;
    }

    // 是否从模板打开
    bool newProjectWithTemplate = false;
    if (2 == nCmdParamsCnt)
    {
        auto itor = cmdParams.find(L"NewProjectWithTemplate");
        newProjectWithTemplate = (itor != cmdParams.end()) ? itor->second.AsBool() : false;
    }

    bool isFamilyInProjectContainer = false;
    std::wstring filePathName;
    if (nCmdParamsCnt == 0)
    {
        {
            std::wstring filter(L"");
            filter = L"Project(*" + GDMPLAB_FILE_EXT + L")";
            filePathName = UiCommonDialog::ShowOpenFileDialog(GBMP_TR(L"打开工程"), gcmp::FileSystem::GetWorkingDirPath(), filter);
        }

        if (filePathName.empty())
        {
            return nullptr;
        }

        FilePath fp(filePathName);
        if (fp.GetExtension() != GDMPLAB_FILE_EXT)
        {
           UiCommonDialog::ShowMessageBox(GBMP_TR(L"不支持的文件格式"), GBMP_TR(L"文件") + fp.GetFileName() + GBMP_TR(L"不是支持的文件格式"), (int)UiCommonDialog::ButtonType::Cancel);
           return nullptr;
        }
        else if (IDocumentManager::Get()->IsFileLinked(filePathName))
        {
            UiCommonDialog::ShowMessageBox(GBMP_TR(L"重复打开文件"), GBMP_TR(L"文件") + fp.GetFileNameWithoutExtension() + GBMP_TR(L"已经被以链接的方式打开，不能重复打开"), (int)UiCommonDialog::ButtonType::Cancel);
            return nullptr;
        }
        else if (IDocumentManager::Get()->IsFileOpened(filePathName))
        {
            UiCommonDialog::ShowMessageBox(GBMP_TR(L"重复打开文件"), GBMP_TR(L"文件") + fp.GetFileNameWithoutExtension() + GBMP_TR(L"已经被打开过，不能重复打开"), (int)UiCommonDialog::ButtonType::Cancel);
            return nullptr;
        }

        // 如果上次异常退出中，让用户选择是否恢复,如果是跨专业打开，不允许异常恢复
        if (!interProfessional && FileRestoreUtils::ExitedAbnormally(filePathName))
        {
            std::wstring  displayfilename = filePathName;
            if (DEBUG_MODE(ShowFileNameWithoutFolder))
            {
                displayfilename = fp.GetFileName();
                
            }
            if (UiCommonDialog::ButtonType::Yes == UiCommonDialog::ShowMessageBox(GBMP_TR(L"提示"), displayfilename + GBMP_TR(L"上次异常退出。是否要恢复？"), (int)UiCommonDialog::ButtonType::Yes | (int)UiCommonDialog::ButtonType::No))
            {
                restore = true;
            }
            if (restore && restoreOnNewFile)
            {
                do {
                    UiCommonDialog::ButtonType buttonType = UiCommonDialog::ShowInputBox(L"请输入恢复出的新文件名", L"新文件名", fp.GetFileNameWithoutExtension() + L" - recover", newFileName);
                    if (UiCommonDialog::ButtonType::OK != buttonType)
                        return nullptr;
                } while (!IsNewFileNameLegal(newFileName, filePathName, newFilePathName));
                if(!FileRestoreUtils::SetRestoreNewFilePath(filePathName, newFilePathName))
                {
                    return nullptr;
                }
            }
        }
    }
    else if (nCmdParamsCnt == 1 ||
        newProjectWithTemplate)
    {
        auto itor = cmdParams.find(L"FilePath");
        DBG_WARN_AND_RETURN_NULLPTR_UNLESS(itor != cmdParams.end(), L"所需的参数FilePath不存在",L"GDMPLab",L"2024-03-30");
        filePathName = LogManager::Get()->GetAbsolutePathForLog(itor->second.AsWString());

        DBG_WARN_AND_RETURN_NULLPTR_UNLESS(FileInfo(filePathName).IsFile(), L"打开文件命令在回放时没有找到指定的文件：" + filePathName,L"GDMPLab",L"2024-03-30");

        if (IDocumentManager::Get()->IsFileLinked(filePathName))
        {
            UiCommonDialog::ShowMessageBox(GBMP_TR(L"重复打开文件"), GBMP_TR(L"文件") + FilePath(filePathName).GetFileNameWithoutExtension() + GBMP_TR(L"已经被以链接的方式打开，不能重复打开"), (int)UiCommonDialog::ButtonType::Cancel);
            return nullptr;
        }
        else if (IDocumentManager::Get()->IsFileOpened(filePathName))
        {
            UiCommonDialog::ShowMessageBox(GBMP_TR(L"重复打开文件"), GBMP_TR(L"文件") + FilePath(filePathName).GetFileNameWithoutExtension() + GBMP_TR(L"已经被打开过，不能重复打开"), (int)UiCommonDialog::ButtonType::Cancel);
            return nullptr;
        }
        // 如果上次异常退出中，让用户选择是否恢复,如果是跨专业打开，不允许异常恢复
        if (!interProfessional && FileRestoreUtils::ExitedAbnormally(filePathName))
        {
            std::wstring  displayfilename = filePathName;
            if (DEBUG_MODE(ShowFileNameWithoutFolder))
            {
                FilePath filepath(filePathName);
                displayfilename = filepath.GetFileName();

            }
            if (UiCommonDialog::ButtonType::Yes == UiCommonDialog::ShowMessageBox(GBMP_TR(L"提示"), displayfilename + GBMP_TR(L"上次异常退出。是否要恢复？"), (int)UiCommonDialog::ButtonType::Yes | (int)UiCommonDialog::ButtonType::No))
            {
                restore = true;
            }
            if (restore && restoreOnNewFile)
            {
                do {
                    UiCommonDialog::ButtonType buttonType = UiCommonDialog::ShowInputBox(L"请输入恢复出的新文件名", L"新文件名", FilePath(filePathName).GetFileNameWithoutExtension() + L" - recover", newFileName);
                    if (UiCommonDialog::ButtonType::OK != buttonType)
                        return nullptr;
                } while (!IsNewFileNameLegal(newFileName, filePathName, newFilePathName));
                if (!FileRestoreUtils::SetRestoreNewFilePath(filePathName, newFilePathName))
                {
                    return nullptr;
                }
            }
        }
    }
    else
    {
        DBG_WARN_AND_RETURN_NULLPTR_UNLESS(false, L"OpenFileCmd的参数个数应该为0或1",L"GDMPLab",L"2024-03-30");
    }

    DocumentLoadResult docLoadResult;
    docLoadResult.OnError = [](const DocumentLoadResult & loadResult) -> bool { 
        // 模拟用户行为:遇到错误继续
        UiCommonDialog::ButtonType choice = UiCommonDialog::ShowMessageBox(GBMP_TR(L"打开文档遇到错误"), loadResult.ErrorMessage + L"你是否继续打开，继续打开结果不可预知", 
            (int)(UiCommonDialog::ButtonType::Yes) | (int)UiCommonDialog::ButtonType::No);
        if (UiCommonDialog::ButtonType::Yes == choice)
        {
            return true;
        }

        return false;
    };

    FilePath filePath(filePathName);
    std::wstring fileName = filePath.GetFileNameWithoutExtension();
    bool savedOrLoaded = true;
    if (nCmdParamsCnt == 2 || //TODO:目前打开文档两个参数代表族管理器编辑状态
        newProjectWithTemplate) // 从模板新建项目的时候将打开之后的文档设置为未保存、未加载，即"新建"
    {
        savedOrLoaded = false;
    }

    bool bReadOnly = false;
    //如果开启，则忽略某些模块的Schema，不加载该模块的所有类Schema,目前跨专业链接使用这个debugMode，跨专业链接文件打开时为只读
    if (DEBUG_MODE(IgnoreModuleClassSchema))
    {
        bReadOnly = true;
    }

    DocumentLoadOptions docLoadOptions(restore, !savedOrLoaded, bReadOnly, interProfessional);
    docLoadOptions.NewFromTemlpate = newProjectWithTemplate;

    if (!newProjectWithTemplate && !isFamilyInProjectContainer)
    {
        // 主文档才可以开启延迟加载验证模式
        bool enableLazyLoad = DEBUG_MODE(EnableLazyLoadInGbmpWhenOpenFile);
        NdbStrategy::Get().EnableLazyLoad(enableLazyLoad);
        docLoadOptions.CanLazyLoad = enableLazyLoad;
    }

    std::set<std::wstring> unrecognizedAssemblies;

    if (isFamilyInProjectContainer)
    {
        docLoadOptions.TakeOwnershipAfterLoaded = false;
    }

    IDocument* pDoc = IDocumentManager::Get()->OpenDocument(filePathName, docLoadOptions, docLoadResult, nullptr, &unrecognizedAssemblies);

    if (!pDoc)
    {
        if (docLoadResult.ErrorCode == DocumentLoadError::FileContainsUnrecognizedElements)
        {
            if (restore)
            {
                std::wstring errorMsg = L"有插件未加载，文件崩溃恢复失败，请加载相应的插件以后再恢复崩溃文件";
                UiCommonDialog::ShowMessageBox(GBMP_TR(L"打开文档错误"), errorMsg, (int)UiCommonDialog::ButtonType::OK);
            }
            else if (!interProfessional && FileRestoreUtils::ExitedAbnormally(filePathName))
            {
                std::wstring errorMsg = L"有插件未加载，请加载相应的插件以后再打开崩溃文件";
                UiCommonDialog::ShowMessageBox(GBMP_TR(L"打开文档错误"), errorMsg, (int)UiCommonDialog::ButtonType::OK);
            }
            else
            {
                std::wstring errorMsg = L"有插件未加载，文件打开失败，请加载相应的插件以后再打开文件";
                UiCommonDialog::ShowMessageBox(GBMP_TR(L"打开文档错误"), errorMsg, (int)UiCommonDialog::ButtonType::OK);
            }
        }
        else
        {
            UiCommonDialog::ShowMessageBox(GBMP_TR(L"打开文档错误"), docLoadResult.ErrorMessage, (int)UiCommonDialog::ButtonType::OK);
        }
        return nullptr;
    }
    else if (pDoc->IsReadonlyWithNoTransactionAndNoSave())
    {
        UiCommonDialog::ShowMessageBox(GBMP_TR(L"打开文档"), GBMP_TR(L"程序没有对文件的修改权限，本次打开是只读模式，不允许修改，保存，另存为"), (int)UiCommonDialog::ButtonType::OK);
    }
    else if (pDoc->IsReadonlyForSharingViolation())
    {
        UiCommonDialog::ShowMessageBox(GBMP_TR(L"打开文档"), GBMP_TR(L"文件已经被另一个程序占用，本次打开是只读模式，不允许保存"), (int)UiCommonDialog::ButtonType::OK);
    }


    if(!pDoc->IsDataDocument())
        UiPlatformUtils::PrepareUI(pDoc);

    return nullptr;
}

gcmp::IUiDocument* OpenFileCmd::GetUiDocument(gcmp::IDocument* pDoc)
{
    if (!pDoc)
        return nullptr;

    IUiDocumentViewManager* pUiDocViewMgr = IUiDocumentViewManager::Get();
    if (!pUiDocViewMgr)
        return nullptr;
    std::vector<IUiDocument*>  pUIDocs = pUiDocViewMgr->GetAllUiDocuments();
    for (auto& pUiDoc : pUIDocs)
    {
        if (pUiDoc->GetDbDocument() && pUiDoc->GetDbDocument()->GetRuntimeId() == pDoc->GetRuntimeId())
        {
            return pUiDoc;
        }
    }

    return nullptr;
}

REGISTER_GM_COMMAND(OpenFileCmd)



